package com.binance.rabbitmq.autoconfigure;

import com.binance.rabbitmq.config.AlternateConfig;
import com.binance.rabbitmq.config.BindingConfig;
import com.binance.rabbitmq.config.DefaultConfig;
import com.binance.rabbitmq.config.DelayConfig;
import com.binance.rabbitmq.config.ExchangeConfig;
import com.binance.rabbitmq.config.ExchangeTypes;
import com.binance.rabbitmq.config.FailConfig;
import com.binance.rabbitmq.config.QueueConfig;
import com.binance.rabbitmq.config.RabbitConfig;
import lombok.extern.log4j.Log4j2;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.CorrelationDataPostProcessor;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 加载exchange,queue,binding
 * Created by robin.wu on 2018/11/29.
 */
@Configuration
@Import(RabbitMqConfiguration.class)
@AutoConfigureAfter(RabbitAutoConfiguration.class)
@ConditionalOnProperty(prefix = "rabbitmq.auto-config", name = "enabled",havingValue = "true")
@Log4j2
public class RabbitMqAutoConfiguration implements ApplicationContextAware {
    private RabbitConfig rabbitConfig;

    private DefaultConfig defaultConfig;

    private RabbitAdmin rabbitAdmin;

    private Map<String, Exchange> exchangeMap;

    private Map<String, Queue> queueMap;

    @Autowired
    public RabbitMqAutoConfiguration(RabbitConfig rabbitExtConfig, DefaultConfig defaultConfig, RabbitAdmin rabbitAdmin) {
        this.rabbitConfig = rabbitExtConfig;
        this.defaultConfig = defaultConfig;
        this.rabbitAdmin = rabbitAdmin;
        this.exchangeMap = new LinkedHashMap<>();
        this.queueMap = new LinkedHashMap<>();
    }

    @PostConstruct
    public void init() {
        Exchange deadLetterExchange = loadDeadLetterExchangeConfig();
        loadExchangeConfigs();
        loadQueueConfigs(deadLetterExchange);
        loadBindingConfigs();
        loadDelayConfig(deadLetterExchange);
        loadFailConfig();
        loadAlternateConfig();
    }

    private Exchange loadDeadLetterExchangeConfig() {
        ExchangeConfig deadLetterExchangeConfig =  (defaultConfig.getDeadLetterConfig() != null && defaultConfig.getDeadLetterConfig().getDeadLetterExchange() != null) ?
                defaultConfig.getDeadLetterConfig().getDeadLetterExchange():ExchangeConfig.builder().name("DEFAULT-DEAD-LETTER-EXCHANGE.DLQ").type(ExchangeTypes.TOPIC).build();
        Exchange deadLetterExchange = deadLetterExchangeConfig.buildExchange(defaultConfig);
        rabbitAdmin.declareExchange(deadLetterExchange);
        log.info("Auto configuring dead letter exchange: Key = {} , DeadLetterExchange = {{}}", deadLetterExchange.getName(), deadLetterExchange);
        return deadLetterExchange;
    }

    private void loadExchangeConfigs() {
        if (!CollectionUtils.isEmpty(rabbitConfig.getExchanges())) {
            log.info("Auto configuring exchange...");
            for (Map.Entry<String, ExchangeConfig> entry : rabbitConfig.getExchanges().entrySet()) {
                Exchange exchange = entry.getValue().buildExchange(defaultConfig);
                exchangeMap.put(entry.getKey(), exchange);
                rabbitAdmin.declareExchange(exchange);
                log.info("Auto configuring exchange: Key = {} , Exchange = {{}}", entry.getKey(), exchange);
            }
        }
    }

    private void loadQueueConfigs(Exchange deadLetterExchange) {
        if (!CollectionUtils.isEmpty(rabbitConfig.getQueues())) {
            log.info("Auto configuring queue...");
            for (Map.Entry<String, QueueConfig> entry : rabbitConfig.getQueues().entrySet()) {
                Queue queue = entry.getValue().buildQueue(defaultConfig.getDefaultQueue(), defaultConfig.getDeadLetterConfig());
                queueMap.put(entry.getKey(), queue);
                rabbitAdmin.declareQueue(queue);
                log.info("Auto configuring queue: Key = {} , Queue = {{}}", entry.getKey(), queue);
                if (entry.getValue().getDeadLetterEnabled()) {
                    Queue deadLetterQueue = entry.getValue().buildDeadLetterQueue(defaultConfig.getDefaultQueue(), defaultConfig.getDeadLetterConfig());
                    rabbitAdmin.declareQueue(deadLetterQueue);
                    log.info("Auto configuring dead letter queue: Key = {} , DeadLetterQueue = {{}}", deadLetterQueue.getName(), deadLetterQueue);
                    Binding deadLetterBinding = BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange).with(deadLetterQueue.getName()).noargs();
                    rabbitAdmin.declareBinding(deadLetterBinding);
                    log.info("Auto configuring dead letter binding: Key = {{}:{}} , DeadLetterBinding = {{}}", deadLetterExchange.getName(), deadLetterQueue.getName(), deadLetterBinding);
                }
            }
        }
    }

    private void loadBindingConfigs() {
        if (!CollectionUtils.isEmpty(rabbitConfig.getBindings())) {
            log.info("Auto configuring binding...");
            for (Map.Entry<String, BindingConfig> entry : rabbitConfig.getBindings().entrySet()) {
                Exchange exchange = exchangeMap.get(entry.getValue().getExchange());
                Queue queue = queueMap.get(entry.getValue().getQueue());
                Binding binding = entry.getValue().bind(exchange, queue);
                rabbitAdmin.declareBinding(binding);
                log.info("Auto configuring binding: Key = {} , Binding = {{}}", entry.getKey(), binding);
            }
        }
    }

    /**
     * 延迟exchange和queue
     * @param deadLetterExchange
     */
    private void loadDelayConfig(Exchange deadLetterExchange) {
        if(defaultConfig.getDelayConfig() != null){
            log.info("Auto configuring delay exchange...");

            DelayConfig delayConfig = defaultConfig.getDelayConfig();
            Exchange delayExchange = delayConfig.getDelayExchange().buildExchange(defaultConfig);
            rabbitAdmin.declareExchange(delayExchange);
            log.info("Auto configuring delay exchange: Key = {} , Exchange = {{}}", delayExchange.getName(), delayExchange);

            for(Map.Entry<String,QueueConfig> entry : delayConfig.getQueues().entrySet()) {
                QueueConfig queueConfig = entry.getValue();
                Queue delayQueue = queueConfig.buildQueue(defaultConfig.getDefaultQueue(),defaultConfig.getDeadLetterConfig());
                rabbitAdmin.declareQueue(delayQueue);
                log.info("Auto configuring delay queue: Key = {} , DelayQueue = {{}}", delayQueue.getName(), delayQueue);

                Binding delayBinding = BindingBuilder.bind(delayQueue).to(delayExchange).with(delayConfig.getRoutingKey()).noargs();
                rabbitAdmin.declareBinding(delayBinding);
                log.info("Auto configuring delay binding: Key = {{}:{}} , DelayBind = {{}}", deadLetterExchange.getName(), delayQueue.getName(), delayBinding);

                if (queueConfig.getDeadLetterEnabled()) {
                    Queue deadLetterQueue = entry.getValue().buildDeadLetterQueue(queueConfig, defaultConfig.getDeadLetterConfig());
                    rabbitAdmin.declareQueue(deadLetterQueue);
                    log.info("Auto configuring dead letter queue: Key = {} , DeadLetterQueue = {{}}", deadLetterQueue.getName(), deadLetterQueue);
                    Binding deadLetterBinding = BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange).with(deadLetterQueue.getName()).noargs();
                    rabbitAdmin.declareBinding(deadLetterBinding);
                    log.info("Auto configuring dead letter binding: Key = {{}:{}} , DeadLetterBinding = {{}}", deadLetterExchange.getName(), deadLetterQueue.getName(), deadLetterBinding);
                }
            }

        }
    }

    /**
     * 失败exchange和queue
     */
    private void loadFailConfig() {
        if(defaultConfig.getFailConfig() != null) {
            log.info("Auto configuring fail exchange...");

            FailConfig failConfig = defaultConfig.getFailConfig();
            Exchange failExchange = failConfig.getFailExchange().buildExchange(defaultConfig);
            rabbitAdmin.declareExchange(failExchange);
            log.info("Auto configuring fail exchange: Key = {} , Exchange = {{}}", failExchange.getName(), failExchange);

            Queue failQueue = failConfig.getFailQueue().buildQueue(defaultConfig.getDefaultQueue(),defaultConfig.getDeadLetterConfig());
            rabbitAdmin.declareQueue(failQueue);
            log.info("Auto configuring fail queue: Key = {} , DelayQueue = {{}}", failQueue.getName(), failQueue);

            Binding failBinding = BindingBuilder.bind(failQueue).to(failExchange).with(failConfig.getRoutingKey()).noargs();
            rabbitAdmin.declareBinding(failBinding);
            log.info("Auto configuring fail binding: Key = {{}:{}} , FailBind = {{}}", failExchange.getName(), failQueue.getName(), failBinding);

        }
    }

    /**
     * 备份exchange/queue配置
     */
    private void loadAlternateConfig() {
        if(defaultConfig.getAlternateConfig() != null) {
            log.info("Auto configuring alternate exchange...");

            AlternateConfig alternateConfig = defaultConfig.getAlternateConfig();
            Exchange alternateExchange = alternateConfig.getAlternateExchange().buildExchange(defaultConfig);
            rabbitAdmin.declareExchange(alternateExchange);
            log.info("Auto configuring alternate exchange: Key = {} , Exchange = {{}}", alternateExchange.getName(), alternateExchange);

            Queue alternateQueue = alternateConfig.getAlternateQueue().buildQueue(defaultConfig.getDefaultQueue(),defaultConfig.getDeadLetterConfig());
            rabbitAdmin.declareQueue(alternateQueue);
            log.info("Auto configuring alternate queue: Key = {} , Queue = {{}}", alternateQueue.getName(), alternateQueue);

            Binding alternateBinding = BindingBuilder.bind(alternateQueue).to(alternateExchange).with(defaultConfig.getAlternateConfig().getRoutingKey()).noargs();
            rabbitAdmin.declareBinding(alternateBinding);
            log.info("Auto configuring alternate binding: Key = {{}:{}} , FailBind = {{}}", alternateExchange.getName(), alternateQueue.getName(), alternateBinding);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        rabbitTemplate.setBeforePublishPostProcessors(applicationContext.getBeansOfType(MessagePostProcessor.class).values().toArray(new MessagePostProcessor[0]));
        rabbitTemplate.setCorrelationDataPostProcessor(applicationContext.getBean(CorrelationDataPostProcessor.class));
    }
}
